﻿using System;
using System.Collections.Generic;
using System.Linq;
using uTILLIty.Rave.Contract.Configuration;
using uTILLIty.Rave.Contract.StorageAdapter;
using uTILLIty.Rave.Core.Configuration;

namespace uTILLIty.Rave.Core.Strategy
{
	/// <summary>
	/// Builds a configuration from an existing directory-structure
	/// </summary>
	public class DefaultConfigurationConstructorStrategy : IConfigurationConstructorStrategy
	{

		/// <summary>
		/// Constructs a <see cref="Contract.Configuration.Configuration"/> from an existing directory
		/// </summary>
    /// <param name="root">The directory to start from</param>
		/// <param name="adapter">The <see cref="IStorageAdapter"/> to use for member-lookup</param>
    /// <param name="recursive">Wether to process just the <paramref name="root"/> directory, or also it's children</param>
		/// <param name="onAfterVisit">Optional action to call after visiting a directory and building it's configuration</param>
		/// <param name="onVisitError">Optional action to call when an error during visiting a directory occurs</param>
    /// <returns>The built <see cref="Contract.Configuration.Configuration"/></returns>
    public virtual Contract.Configuration.Configuration BuildFromDirectory(string root, IStorageAdapter adapter, bool recursive, 
      Action<ConfigurationExtensions.VisitingContext> onAfterVisit, Action<ConfigurationExtensions.VisitingError> onVisitError)
		{
      if (adapter == null)
        throw new ArgumentNullException("adapter");
      if (string.IsNullOrWhiteSpace(root))
        throw new ArgumentNullException("root");

			var members = new List<Member>(20);

      string parentPath;
		  string dirName = adapter.GetDirectoryName(root, out parentPath);
      var parentDirContext = (parentPath != null) ? new DirectoryContext(new Directory(parentPath), null) : null;
      var dirContext = new DirectoryContext(new Directory(dirName), parentDirContext);
      if (!adapter.CheckDirectoryExists(dirContext))
        throw new ArgumentException(string.Format(Properties.ExceptionMessages.DirectoryMustExist, root), "root");

      var config = new Contract.Configuration.Configuration(adapter.TargetSystem, parentPath ?? root);

      ReadDirectoryStructure(dirContext, adapter, recursive);

      config.Directories = new[] { dirContext.Directory };

			config.VisitDirectories(
        context => VisitDirectory(context, members, adapter, onAfterVisit), 
        context => HandleVisitingError(context, onVisitError));

			config.Members = members.ToArray();
		  return config;
		}

		/// <summary>
		/// Retrieves the directory's ACLs
		/// </summary>
		protected virtual void VisitDirectory(ConfigurationExtensions.VisitingContext context, List<Member> members, IStorageAdapter adapter, Action<ConfigurationExtensions.VisitingContext> onAfterVisit)
		{
			var di = context.DirectoryContext;

			bool containsInheritedAcls;
			List<SecurityEntryContext> entries = adapter.GetExistingSecurityEntries(di, out containsInheritedAcls);
			di.Directory.RemoveSecurityEntriesInheritance = !containsInheritedAcls;
			di.Directory.RemoveAllOtherSecurityEntries = !containsInheritedAcls;

			foreach (var entry in entries)
			{
				CreateSecurityEntry(entry, adapter);
				TryAddMember(entry, members);
			}
			di.Directory.SecurityEntries = entries.Select(e => e.TemplateSecurityEntry).ToArray();

			if (onAfterVisit != null)
				onAfterVisit.Invoke(context);
		}

		/// <summary>
		/// Constructs a <see cref="SecurityEntry"/> from the given <paramref name="entryContext"/>
		/// </summary>
		protected virtual void CreateSecurityEntry(SecurityEntryContext entryContext, IStorageAdapter adapter)
		{
			MemberType memberType = adapter.GetMemberType(entryContext.StorageMember.TargetMember.ToString());
			entryContext.TemplateSecurityEntry = new SecurityEntry(memberType)
				{
					MemberID = entryContext.StorageMember.TargetMember.ToString(),
					InheritanceFlags = entryContext.CurrentInheritanceFlags,
					PropagationFlags = entryContext.CurrentPropagationFlags,
					Rights = entryContext.CurrentRights,
					AccessControlType = entryContext.AccessControlType
				};
		}

		/// <summary>
		/// Rethrow is set to true. VisiChildren is set to false. 
		/// Calls the supplied <paramref name="onVisitError"/>, if set (which could log and set rethrow to false)
		/// </summary>
		protected virtual void HandleVisitingError(ConfigurationExtensions.VisitingError context, Action<ConfigurationExtensions.VisitingError> onVisitError)
		{
			context.Rethrow = true;
			context.VisitChildren = false;
			if (onVisitError != null)
				onVisitError.Invoke(context);
		}

		/// <summary>
		/// Searches for a member of the same ID within the supplied (configuration's) members collection and
		/// creates a member, if required (not found)
		/// </summary>
		protected virtual void TryAddMember(SecurityEntryContext entry, ICollection<Member> members)
		{
			var id = entry.TemplateSecurityEntry.MemberID;
			if (members.Any(m => m.ID.Equals(id)))
				return;

			var member = new Member(id, TryRemoveDomain(id), "samAccountName", entry.TemplateSecurityEntry.MemberType);

			members.Add(member);
		}

		/// <summary>
		/// Parses the supplied ID for a backslash (\) and removes the domain (first) part
		/// </summary>
		protected virtual string TryRemoveDomain(string id)
		{
			var values = id.Split(new[] { '\\' });
			return values.Length > 1 ? values[1] : values[0];
		}

		/// <summary>
		/// Recusrively reads the physical directory structure and fills the directory-hierarchy
		/// </summary>
    protected virtual void ReadDirectoryStructure(DirectoryContext currentDir, IStorageAdapter adapter, bool recursive)
		{
			currentDir.Directory.SkipProcessingIfDirectoryExists = true; //set so it is rendered as XML (for user to see and change)
		  currentDir.Directory.SubDirectories = adapter.GetExistingDirectories(currentDir);

			if (!recursive)
				return;

      foreach (var subDir in currentDir.Directory.SubDirectories)
			{
				ReadDirectoryStructure(new DirectoryContext(subDir, currentDir), adapter, true);
			}
		}		 
	}
}